package top.v5it.japi.ddd.core.dao.impl.wrapper;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.func.Func1;
import cn.hutool.core.lang.func.LambdaUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import top.v5it.japi.ddd.core.dao.impl.DaoHelper;
import top.v5it.japi.ddd.core.dao.impl.EntityHelper;
import top.v5it.japi.ddd.core.dao.impl.SqlKeyword;
import top.v5it.japi.ddd.core.dao.impl.Wrapper;
import top.v5it.japi.ddd.core.exception.QueryException;
import top.v5it.japi.ddd.core.util.BeanUtils;

import java.util.*;
import java.util.function.BiFunction;
import java.util.stream.Collectors;

/**
 * 包装sql
 *
 * @param <T> 实体类型
 * @param <W> 子类类型，用于方法链式调用
 * @date 2023/11/15
 */
public class SqlWrapper<T, W extends SqlWrapper<T, W>> extends Wrapper<T> {

    protected final W wrapper;
    protected final Map<String, Join> entities = new HashMap<>();
    protected final List<String> condition = new ArrayList<>();
    protected final static String COLUMN_TEMPLATE = "{}.`{}`";
    private final static String COLUMN_CONDITION_LEFT_TEMPLATE = COLUMN_TEMPLATE + " {}";
    private final static String SELECT_TEMPLATE = "SELECT {}.* FROM `{}` {}";
    private final static String TABLE_ALIAS_NAME_PREFIX = "T"; // 表别名前缀
    private int tableAliasNameSuffixCounter = 0; // 表别名后缀
    private int nameSuffixCounter = 0; // 条件参数名称后缀
    private String sql;

    /**
     * 构造方法
     *
     * @param entity 实体对象
     */
    @SuppressWarnings("unchecked")
    protected SqlWrapper(T entity) {
        super(entity);
        this.wrapper = (W) this;
        entities.put(getAsName() + tableAliasNameSuffixCounter, new SqlWrapper.Join(null, entity));
    }

    /**
     * 按输入的本地sql语句查询
     * <pre>
     *     select * from table_name
     * </pre>
     *
     * @param sql 输入的本地sql语句
     * @return 当前实例
     */
    public W sql(String sql) {
        this.sql = sql;
        return wrapper;
    }

    /**
     * 设置需要查询的字段
     * <pre>
     *     Entity::getXXX
     * </pre>
     *
     * @param columnFunc 字段的获取函数
     * @param <L>        字段所属实体类型
     * @return 当前实例
     */
    public <L> W select(Func1<L, ?> columnFunc) {
        getFieldName(columnFunc)
                .ifPresent(t -> addColumnSegment(StrUtil.format(COLUMN_TEMPLATE, t.getAlias(), t.getName())));
        return wrapper;
    }

    /**
     * 进行关联查询
     *
     * @param entity   关联查询的实体对象
     * @param joinType 连接类型，如LEFT JOIN、RIGHT JOIN等
     * @param <R>      关联查询的实体类型
     * @return 当前实例
     */
    public <R> W join(R entity, String joinType) {
        tableAliasNameSuffixCounter++;
        final String alias = TABLE_ALIAS_NAME_PREFIX + tableAliasNameSuffixCounter;
        entities.put(alias, new SqlWrapper.Join(joinType, entity));
        return wrapper;
    }

    /**
     * 进行左连接查询
     *
     * @param entity 关联查询的实体对象
     * @param <R>    关联查询的实体类型
     * @return 当前实例
     */
    public <R> W leftJoin(R entity) {
        return join(entity, "LEFT JOIN");
    }

    /**
     * 进行右连接查询
     *
     * @param entity 关联查询的实体对象
     * @param <R>    关联查询的实体类型
     * @return 当前实例
     */
    public <R> W rightJoin(R entity) {
        return join(entity, "RIGHT JOIN");
    }

    /**
     * 设置关联查询的条件
     *
     * @param leftColumnFunc  左边表字段获取函数
     * @param rightColumnFunc 右边表字段获取函数
     * @param <L>             左边表字段所属实体类型
     * @param <R>             右边表字段所属实体类型
     * @return 当前实例
     */
    public <L, R> W on(Func1<L, ?> leftColumnFunc, Func1<R, ?> rightColumnFunc) {
        final BiFunction<String, String, String> mapper = (t, u) -> StrUtil.format(COLUMN_TEMPLATE, t, u);
        final Class<L> leftRealClass = LambdaUtil.getRealClass(leftColumnFunc);
        final Class<R> rightRealClass = LambdaUtil.getRealClass(rightColumnFunc);
        String left = null, right;
        for (Map.Entry<String, SqlWrapper.Join> entry : entities.entrySet()) {
            boolean matched = ClassUtil.isAssignable(entry.getValue().getEntity().getClass(), leftRealClass);
            if (matched && Objects.isNull(left)) {
                final String leftFieldName = LambdaUtil.getFieldName(leftColumnFunc);
                final String key = entry.getKey();
                left = mapper.apply(key, leftFieldName);
                continue;
            }
            matched = ClassUtil.isAssignable(entry.getValue().getEntity().getClass(), rightRealClass);
            if (matched) {
                final String rightFieldName = LambdaUtil.getFieldName(rightColumnFunc);
                final String key = entry.getKey();
                right = mapper.apply(key, rightFieldName);
                entry.getValue().getOnCondition().add(left + " = " + right);
                break;
            }
        }
        return wrapper;
    }

    /**
     * 设置关联查询的条件，与on方法作用相同
     *
     * @param leftColumnFunc  左边表字段获取函数
     * @param rightColumnFunc 右边表字段获取函数
     * @param <L>             左边表字段所属实体类型
     * @param <R>             右边表字段所属实体类型
     * @return 当前实例
     */
    public <L, R> W and(Func1<L, ?> leftColumnFunc, Func1<R, ?> rightColumnFunc) {
        return on(leftColumnFunc, rightColumnFunc);
    }

    /**
     * 添加等于条件
     *
     * @param columnFunc 字段获取函数
     * @param <L>        字段所属实体类型
     * @return 当前实例
     */
    public <L> W eq(Func1<L, ?> columnFunc) {
        condition(columnFunc, SqlKeyword.EQ);
        return wrapper;
    }

    /**
     * 添加等于条件，并指定值
     *
     * @param columnFunc 字段获取函数
     * @param value      值
     * @param <L>        字段所属实体类型
     * @return 当前实例
     */
    public <L> W eq(Func1<L, ?> columnFunc, Object value) {
        Assert.notNull(value, () -> new QueryException("[" + SqlKeyword.EQ + "]，参数[value]不能为空"));
        condition(columnFunc, value, SqlKeyword.EQ);
        return wrapper;
    }

    /**
     * 添加大于条件
     *
     * @param columnFunc 字段获取函数
     * @param <L>        字段所属实体类型
     * @return 当前实例
     */
    public <L> W gt(Func1<L, ?> columnFunc) {
        condition(columnFunc, SqlKeyword.GT);
        return wrapper;
    }

    /**
     * 添加大于条件，并指定值
     *
     * @param columnFunc 字段获取函数
     * @param value      值
     * @param <L>        字段所属实体类型
     * @return 当前实例
     */
    public <L> W gt(Func1<L, ?> columnFunc, Object value) {
        Assert.notNull(value, () -> new QueryException("[" + SqlKeyword.GT + "]，参数[value]不能为空"));
        condition(columnFunc, value, SqlKeyword.GT);
        return wrapper;
    }

    /**
     * 添加小于条件
     *
     * @param columnFunc 字段获取函数
     * @param <L>        字段所属实体类型
     * @return 当前实例
     */
    public <L> W lt(Func1<L, ?> columnFunc) {
        condition(columnFunc, SqlKeyword.LT);
        return wrapper;
    }

    /**
     * 添加小于条件，并指定值
     *
     * @param columnFunc 字段获取函数
     * @param value      值
     * @param <L>        字段所属实体类型
     * @return 当前实例
     */
    public <L> W lt(Func1<L, ?> columnFunc, Object value) {
        Assert.notNull(value, () -> new QueryException("[" + SqlKeyword.LT + "]，参数[value]不能为空"));
        condition(columnFunc, value, SqlKeyword.LT);
        return wrapper;
    }

    /**
     * 添加不等于条件
     *
     * @param columnFunc 字段获取函数
     * @param <L>        字段所属实体类型
     * @return 当前实例
     */
    public <L> W ne(Func1<L, ?> columnFunc) {
        condition(columnFunc, SqlKeyword.NE);
        return wrapper;
    }

    /**
     * 添加不等于条件，并指定值
     *
     * @param columnFunc 字段获取函数
     * @param value      值
     * @param <L>        字段所属实体类型
     * @return 当前实例
     */
    public <L> W ne(Func1<L, ?> columnFunc, Object value) {
        Assert.notNull(value, () -> new QueryException("[" + SqlKeyword.NE + "]，参数[value]不能为空"));
        condition(columnFunc, value, SqlKeyword.NE);
        return wrapper;
    }

    /**
     * 添加大于等于条件
     *
     * @param columnFunc 字段获取函数
     * @param <L>        字段所属实体类型
     * @return 当前实例
     */
    public <L> W ge(Func1<L, ?> columnFunc) {
        condition(columnFunc, SqlKeyword.GE);
        return wrapper;
    }

    /**
     * 添加大于等于条件，并指定值
     *
     * @param columnFunc 字段获取函数
     * @param value      值
     * @param <L>        字段所属实体类型
     * @return 当前实例
     */
    public <L> W ge(Func1<L, ?> columnFunc, Object value) {
        Assert.notNull(value, () -> new QueryException("[" + SqlKeyword.GE + "]，参数[value]不能为空"));
        condition(columnFunc, value, SqlKeyword.GE);
        return wrapper;
    }

    /**
     * 添加小于等于条件
     *
     * @param columnFunc 字段获取函数
     * @param <L>        字段所属实体类型
     * @return 当前实例
     */
    public <L> W le(Func1<L, ?> columnFunc) {
        condition(columnFunc, SqlKeyword.LE);
        return wrapper;
    }

    /**
     * 添加小于等于条件，并指定值
     *
     * @param columnFunc 字段获取函数
     * @param value      值
     * @param <L>        字段所属实体类型
     * @return 当前实例
     */
    public <L> W le(Func1<L, ?> columnFunc, Object value) {
        Assert.notNull(value, () -> new QueryException("[" + SqlKeyword.LE + "]，参数[value]不能为空"));
        condition(columnFunc, value, SqlKeyword.LE);
        return wrapper;
    }

    /**
     * 添加为空条件
     *
     * @param columnFunc 字段获取函数
     * @param <L>        字段所属实体类型
     * @return 当前实例
     */
    public <L> W isNull(Func1<L, ?> columnFunc) {
        getFieldName(columnFunc)
                .ifPresent(t -> condition.add(StrUtil.format(COLUMN_CONDITION_LEFT_TEMPLATE, t.getAlias(), t.getName(), SqlKeyword.IS_NULL.getKeyword())));
        return wrapper;
    }

    /**
     * 添加不为空条件
     *
     * @param columnFunc 字段获取函数
     * @param <L>        字段所属实体类型
     * @return 当前实例
     */
    public <L> W isNotNull(Func1<L, ?> columnFunc) {
        getFieldName(columnFunc)
                .ifPresent(t -> condition.add(StrUtil.format(COLUMN_CONDITION_LEFT_TEMPLATE, t.getAlias(), t.getName(), SqlKeyword.IS_NOT_NULL.getKeyword())));
        return wrapper;
    }

    /**
     * 添加模糊匹配条件，使用LIKE '%value%'形式
     *
     * @param columnFunc 字段获取函数
     * @param value      值
     * @param <L>        字段所属实体类型
     * @return 当前实例
     */
    public <L> W like(Func1<L, ?> columnFunc, Object value) {
        Assert.notNull(value, () -> new QueryException("[" + SqlKeyword.LIKE + "]，参数[value]不能为空"));
        getFieldName(columnFunc)
                .ifPresent(t -> {
                    final String keyName = getKeyName(t.getName());
                    condition.add(StrUtil.format(COLUMN_CONDITION_LEFT_TEMPLATE + " CONCAT('%', #{val.{}}, '%')", t.getAlias(), t.getName(), SqlKeyword.LIKE.getKeyword(), keyName));
                    addValue(keyName, value);
                });
        return wrapper;
    }

    /**
     * 添加模糊匹配条件，使用NOT LIKE '%value%'形式
     *
     * @param columnFunc 字段获取函数
     * @param value      值
     * @param <L>        字段所属实体类型
     * @return 当前实例
     */
    public <L> W notLike(Func1<L, ?> columnFunc, Object value) {
        Assert.notNull(value, () -> new QueryException("[" + SqlKeyword.LIKE + "]，参数[value]不能为空"));
        getFieldName(columnFunc)
                .ifPresent(t -> {
                    final String keyName = getKeyName(t.getName());
                    condition.add(StrUtil.format(COLUMN_CONDITION_LEFT_TEMPLATE + " CONCAT('%', #{val.{}}, '%')", t.getAlias(), t.getName(), SqlKeyword.NOT_LIKE.getKeyword(), keyName));
                    addValue(keyName, value);
                });
        return wrapper;
    }

    /**
     * 添加模糊匹配条件，使用LIKE 'value%'形式
     *
     * @param columnFunc 字段获取函数
     * @param value      值
     * @param <L>        字段所属实体类型
     * @return 当前实例
     */
    public <L> W likeLeft(Func1<L, ?> columnFunc, Object value) {
        Assert.notNull(value, () -> new QueryException("[" + SqlKeyword.LIKE + "]，参数[value]不能为空"));
        getFieldName(columnFunc)
                .ifPresent(t -> {
                    final String keyName = getKeyName(t.getName());
                    condition.add(StrUtil.format(COLUMN_CONDITION_LEFT_TEMPLATE + " CONCAT('%', #{val.{}})", t.getAlias(), t.getName(), SqlKeyword.LIKE.getKeyword(), keyName));
                    addValue(keyName, value);
                });
        return wrapper;
    }

    /**
     * 添加模糊匹配条件，使用LIKE '%value'形式
     *
     * @param columnFunc 字段获取函数
     * @param value      值
     * @param <L>        字段所属实体类型
     * @return 当前实例
     */
    public <L> W likeRight(Func1<L, ?> columnFunc, Object value) {
        Assert.notNull(value, () -> new QueryException("[" + SqlKeyword.LIKE + "]，参数[value]不能为空"));
        getFieldName(columnFunc)
                .ifPresent(t -> {
                    final String keyName = getKeyName(t.getName());
                    condition.add(StrUtil.format(COLUMN_CONDITION_LEFT_TEMPLATE + " CONCAT(#{val.{}}, '%')", t.getAlias(), t.getName(), SqlKeyword.LIKE.getKeyword(), keyName));
                    addValue(keyName, value);
                });
        return wrapper;
    }

    /**
     * 添加区间条件，包括起始值和结束值
     *
     * @param columnFunc 字段获取函数
     * @param first      起始值
     * @param next       结束值
     * @param <L>        字段所属实体类型
     * @return 当前实例
     */
    public <L> W between(Func1<L, ?> columnFunc, Object first, Object next) {
        Assert.notNull(first, () -> new QueryException("[" + SqlKeyword.BETWEEN + "]，参数[first]不能为空"));
        Assert.notNull(next, () -> new QueryException("[" + SqlKeyword.BETWEEN + "]，参数[next]不能为空"));
        getFieldName(columnFunc)
                .ifPresent(t -> {
                    final String fieldName = t.getName();
                    final String start = getKeyName(fieldName);
                    final String end = getKeyName(fieldName);
                    condition.add(StrUtil.format(COLUMN_CONDITION_LEFT_TEMPLATE + " #{val.{}} AND #{val.{}}", t.getAlias(), fieldName, SqlKeyword.BETWEEN.getKeyword(), start, end));
                    addValue(start, first);
                    addValue(end, next);
                });
        return wrapper;
    }

    /**
     * 添加非区间条件，排除起始值和结束值之间的值
     *
     * @param columnFunc 字段获取函数
     * @param first      起始值
     * @param next       结束值
     * @param <L>        字段所属实体类型
     * @return 当前实例
     */
    public <L> W notBetween(Func1<L, ?> columnFunc, Object first, Object next) {
        Assert.notNull(first, () -> new QueryException("[" + SqlKeyword.BETWEEN + "]，参数[first]不能为空"));
        Assert.notNull(next, () -> new QueryException("[" + SqlKeyword.BETWEEN + "]，参数[next]不能为空"));
        getFieldName(columnFunc)
                .ifPresent(t -> {
                    final String fieldName = t.getName();
                    final String start = getKeyName(fieldName);
                    final String end = getKeyName(fieldName);
                    condition.add(StrUtil.format(COLUMN_CONDITION_LEFT_TEMPLATE + " #{val.{}} AND #{val.{}}", t.getAlias(), fieldName, SqlKeyword.NOT_BETWEEN.getKeyword(), start, end));
                    addValue(start, first);
                    addValue(end, next);
                });
        return wrapper;
    }

    /**
     * 添加子查询条件，判断子查询的结果是否存在
     *
     * @param wrapper 子查询的SqlWrapper对象
     * @param <T1>    子查询的实体类型
     * @param <W1>    子查询的SqlWrapper子类类型
     * @return 当前实例
     */
    public <T1, W1 extends SubSqlWrapper<T1, W1>> W exists(SqlWrapper<T1, W1> wrapper) {
        final String existsSql = wrapper.getSql(this);
        condition.add(StrUtil.format("{} ({})", SqlKeyword.EXISTS.getKeyword(), existsSql));
        return this.wrapper;
    }

    /**
     * 添加子查询条件，判断子查询的结果是否不存在
     *
     * @param wrapper 子查询的SqlWrapper对象
     * @param <T1>    子查询的实体类型
     * @param <W1>    子查询的SqlWrapper子类类型
     * @return 当前实例
     */
    public <T1, W1 extends SubSqlWrapper<T1, W1>> W notExists(SqlWrapper<T1, W1> wrapper) {
        final String existsSql = wrapper.getSql(this);
        condition.add(StrUtil.format("{} ({})", SqlKeyword.NOT_EXISTS.getKeyword(), existsSql));
        return this.wrapper;
    }

    /**
     * 添加IN条件，判断字段值是否在指定集合内
     *
     * @param columnFunc 字段获取函数
     * @param value      值集合
     * @param <L>        字段所属实体类型
     * @return 当前实例
     */
    public <L> W in(Func1<L, ?> columnFunc, Object value) {
        Assert.notNull(value, () -> new QueryException("[" + SqlKeyword.IN + "]，参数[value]不能为空"));
        if (!(value instanceof Iterable || ArrayUtil.isArray(value))) return eq(columnFunc, value);
        getFieldName(columnFunc).ifPresent(t -> in(t, value, SqlKeyword.IN));
        return wrapper;
    }

    /**
     * 添加NOT IN条件，判断字段值是否不在指定集合内
     *
     * @param columnFunc 字段获取函数
     * @param value      值集合
     * @param <L>        字段所属实体类型
     * @return 当前实例
     */
    public <L> W not(Func1<L, ?> columnFunc, Object value) {
        Assert.notNull(value, () -> new QueryException("[" + SqlKeyword.NOT_IN + "]，参数[value]不能为空"));
        if (!(value instanceof Iterable || ArrayUtil.isArray(value))) return eq(columnFunc, value);
        getFieldName(columnFunc).ifPresent(t -> in(t, value, SqlKeyword.NOT_IN));
        return wrapper;
    }

    protected String getSelect(String as, String tableName) {
        final List<String> columnSegments = getColumnSegments();
        if (CollUtil.isEmpty(columnSegments)) return StrUtil.format(SELECT_TEMPLATE, as, tableName, as);
        return columnSegments.stream()
                .collect(Collectors.joining(", ", "SELECT ", " FROM `" + tableName + "` " + as + ""));
    }

    /**
     * 获取最终的SQL语句
     *
     * @return SQL语句
     */
    @Override
    protected String getSql() {
        final StringBuilder sb = new StringBuilder();
        if (Objects.isNull(sql) && CollUtil.isNotEmpty(entities)) {
            boolean isFirstEntity = true;
            for (Map.Entry<String, SqlWrapper.Join> entry : entities.entrySet()) {
                DaoHelper helper = EntityHelper.readDataFromEntity(entry.getValue().getEntity());
                final String key = entry.getKey();
                final String tableName = helper.getTableName();
                final String keyword = entry.getValue().getKeyword();
                final List<String> onConditions = entry.getValue().getOnCondition();
                if (isFirstEntity) {
                    final String sql = this.getSelect(key, tableName);
                    sb.append(sql);
                    isFirstEntity = false;
                } else {
                    sb.append(" ").append(keyword).append(" `").append(tableName).append("` ")
                            .append(key).append(onConditions.stream().collect(Collectors.joining(" AND ", " ON ", "")));
                }
            }
            if (CollUtil.isNotEmpty(condition)) {
                sb.append(condition.stream().collect(Collectors.joining(" AND ", " WHERE ", "")));
            }
            return sb.toString();
        }
        return sql;
    }

    protected <T1, W1 extends SqlWrapper<T1, W1>> String getSql(SqlWrapper<T1, W1> wrapper) {
        return null;
    }

    protected String getAsName() {
        return TABLE_ALIAS_NAME_PREFIX;
    }

    /**
     * 获取条件参数的键值对
     *
     * @return 条件参数的键值对
     */
    @Override
    protected Map<String, Object> getValue() {
        return super.getValue();
    }

    private <L> void condition(Func1<L, ?> columnFunc, SqlKeyword sqlKeyword) {
        getFieldName(columnFunc)
                .ifPresent(t -> {
                    final String keyName = getKeyName(t.getName());
                    final Object value = BeanUtils.getValueByField(getEntity(), t.getName());
                    if (Objects.isNull(value)) return;
                    condition.add(StrUtil.format(COLUMN_CONDITION_LEFT_TEMPLATE + " #{val.{}}", t.getAlias(), t.getName(), sqlKeyword.getKeyword(), keyName));
                    addValue(keyName, value);
                });
    }

    private <L> void condition(Func1<L, ?> columnFunc, Object value, SqlKeyword sqlKeyword) {
        if (Objects.isNull(value)) return;
        getFieldName(columnFunc)
                .ifPresent(t -> {
                    final String keyName = getKeyName(t.getName());
                    condition.add(StrUtil.format(COLUMN_CONDITION_LEFT_TEMPLATE + " #{val.{}}", t.getAlias(), t.getName(), sqlKeyword.getKeyword(), keyName));
                    addValue(keyName, value);
                });
    }

    private String getKeyName(String fieldName) {
        nameSuffixCounter++;
        return fieldName + nameSuffixCounter;
    }

    private <L> Optional<Field> getFieldName(Func1<L, ?> columnFunc) {
        final Class<L> columnRealClass = LambdaUtil.getRealClass(columnFunc);
        for (Map.Entry<String, SqlWrapper.Join> entry : entities.entrySet()) {
            boolean matched = ClassUtil.isAssignable(entry.getValue().getEntity().getClass(), columnRealClass);
            if (matched) {
                final String fieldName = LambdaUtil.getFieldName(columnFunc);
                final String alias = entry.getKey();
                return Optional.of(new Field(alias, fieldName));
            }
        }
        return Optional.empty();
    }

    private void in(Field field, Object value, SqlKeyword keyword) {
        final String keyName = getKeyName(field.getName());
        final int length = ObjectUtil.length(value);
        final List<String> list = new ArrayList<>();
        for (int i = 0; i < length; i++) list.add("#{val." + keyName + "[" + i + "]}");
        final String in = list.stream().collect(Collectors.joining(", ", "(", ")"));
        condition.add(StrUtil.format(COLUMN_CONDITION_LEFT_TEMPLATE + " {}", field.getAlias(), field.getName(), keyword.getKeyword(), in));
        addValue(keyName, value);
    }

    /**
     * 创建SqlWrapper实例
     *
     * @param entity 实体对象
     * @param <T>    实体类型
     * @param <W>    子类类型
     * @return SqlWrapper实例
     */
    public static <T, W extends SubSqlWrapper<T, W>> SubSqlWrapper<T, W> createSubSqlWrapper(T entity) {
        return new SubSqlWrapper<>(entity);
    }

    @Getter
    @RequiredArgsConstructor
    static class Join {
        private final String keyword;
        private final Object entity;
        private final List<String> onCondition = new ArrayList<>();
    }

    @Getter
    @RequiredArgsConstructor
    static class Field {
        private final String alias;
        private final String name;
    }
}
